package epoch.game.core.serialize;

import static epoch.game.core.serialize.WireFormat.ARRAY;
import static epoch.game.core.serialize.WireFormat.BOOLEAN;
import static epoch.game.core.serialize.WireFormat.BYTE;
import static epoch.game.core.serialize.WireFormat.CHAR;
import static epoch.game.core.serialize.WireFormat.COLLECTION;
import static epoch.game.core.serialize.WireFormat.CONHASHMAP;
import static epoch.game.core.serialize.WireFormat.DISTRIBUTED;
import static epoch.game.core.serialize.WireFormat.DOUBLE;
import static epoch.game.core.serialize.WireFormat.ENUM;
import static epoch.game.core.serialize.WireFormat.FLOAT;
import static epoch.game.core.serialize.WireFormat.HASHMAP;
import static epoch.game.core.serialize.WireFormat.INT;
import static epoch.game.core.serialize.WireFormat.LINKHASHMAP;
import static epoch.game.core.serialize.WireFormat.LINKLIST;
import static epoch.game.core.serialize.WireFormat.LIST;
import static epoch.game.core.serialize.WireFormat.LONG;
import static epoch.game.core.serialize.WireFormat.NULL;
import static epoch.game.core.serialize.WireFormat.OBJECT;
import static epoch.game.core.serialize.WireFormat.SET;
import static epoch.game.core.serialize.WireFormat.SHORT;
import static epoch.game.core.serialize.WireFormat.STRING;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.logging.log4j.Logger;

import epoch.game.core.buffer.IBuffer;
import epoch.game.core.common.Loggers;
import epoch.game.core.exception.DistrSerilizableException;
import epoch.game.core.exception.SysException;

/**
 * 缓冲区
 * 
 * @author will
 * @date 2016年3月23日 下午8:11:06
 */
public class StreamWriter implements IStreamWriter {

	private static Logger log = Loggers.CORE_LOGGER;
	
	public static int DEFUALT_BUFFER_SIZE=512;
	
	// 写入流
	private IBuffer buffer;
	
	public StreamWriter(){
		this(DEFUALT_BUFFER_SIZE);
	}
	
	public StreamWriter(int bufferSize){
		buffer = IBuffer.alloc(bufferSize);
	}
	
	public StreamWriter(int bufferSize, boolean autoExpand, boolean autoShrink){
		buffer = IBuffer.alloc(bufferSize);
	}
	
	/**
	 * return byte array
	 * @return
	 */
	public byte[] array() {
		return buffer.array();
	}

	@Override
	public void writeByte(byte value) throws IOException {
		buffer.writeByte(value);
	}

	@Override
	public void writeShort(short value) throws IOException {
		buffer.writeShort(value);
	}
	
	@Override
	public void writeChar(char value) throws IOException {
		buffer.writeChar(value);
	}

	@Override
	public void writeInt(int value) throws IOException {
		buffer.writeInt(value);
	}

	@Override
	public void writeLong(long value) throws IOException {
		buffer.writeLong(value);
	}

	@Override
	public void writeFloat(float value) throws IOException {
		buffer.writeFloat(value);
	}

	@Override
	public void writeDouble(double value) throws IOException {
		buffer.writeDouble(value);
	}
	
	@Override
	public void writeBoolean(boolean value) throws IOException {
		buffer.writeByte((byte)(value?1:0));
	}

	@Override
	public void writeString(String value) throws IOException {
		if (value == null) {
			buffer.writeByte(WireFormat.NULL);
		} else {
			buffer.writeByte(WireFormat.STRING);
			try {
				byte[] bytes = value.getBytes(WireFormat.STRING_CHARSET);
				buffer.writeInt(bytes.length);
				buffer.writeBytes(bytes);
			} catch (UnsupportedEncodingException e) {
				log.error("writeString encoding error:" + e.getMessage());
			}
		}
	}

	
	/**
	 * 写入数据到流中
	 * 仅支持
	 * byte byte[] boolean boolean[] int int[] long long[] 
	 * double double[] String String[] 
	 * Enum枚举 List、Map两种包装类型
	 * 以及实现了IDistributedSerilizable接口的类
	 * @param value
	 * @throws IOException
	 */
	@Override
	public void write(Object value) throws IOException{
		try {
			writeObject(value);
		//不支持串行话的错误 要对外汇报
		} catch(DistrSerilizableException e) {
			throw e;
//		} catch(OutOfSpaceException | StreamOutOfSpaceException e) {
//			throw new StreamOutOfSpaceException(e, "写入数据内容过大，超过了剩余空间：len={}, used={}, value={}", lengthTotal, getLength(), value);
		} catch (Exception e) {
			throw new SysException(e, "OutputStream写入数据失败。");
		}
	}
	
	@Override
	public void writeObject(ISerializable object) {
		if (object == null) {
			buffer.writeByte(WireFormat.NULL);
		} else {
			buffer.writeByte(WireFormat.DISTRIBUTED);
			try {
				object.writeTo(this);
			} catch (IOException ex) {
				Loggers.CORE_LOGGER.error("序列化失败，class="+this.getClass(),ex);
				throw new SysException("序列化失败，class="+this.getClass(), ex);
			}
		}
	}
	
	/**
	 * 写入数据到流中
	 * 仅支持
	 * byte byte[] boolean boolean[] int int[] long long[] 
	 * double double[] String String[] 
	 * Enum枚举 List、Map两种包装类型
	 * 以及实现了IDistributedSerilizable接口的类
	 * @param value
	 * @throws IOException
	 */
	private void writeObject(Object value) throws IOException {
		//空对象
		if(value == null) {
			buffer.writeByte(NULL);
			return;
		}
		
		//数据类型
		Class<?> clazz = value.getClass();

		//BYTE
		if(clazz == byte.class || clazz==Byte.class){//原始类型不处理
			buffer.writeByte(BYTE);
			buffer.writeByte((byte)value);
		}else if(clazz==byte[].class || clazz==Byte[].class){
			byte tag = BYTE|ARRAY;
			buffer.writeByte(tag);
			byte[] bytes = (byte[])value;
			buffer.writeInt(bytes.length);
			buffer.writeBytes(bytes);
		//BOOLEAN
		} else if(clazz == boolean.class || clazz == Boolean.class) {
			buffer.writeByte(BOOLEAN);
			writeBoolean((boolean)value);
		} else if(clazz == boolean[].class || Boolean[].class==clazz) {
			byte tag = BOOLEAN|ARRAY;
			buffer.writeByte(tag);
			boolean[] array = (boolean[])value;
			writeInt(array.length);
			for(int i = 0; i < array.length; i++) {
				writeBoolean(array[i]);
			}
		//SHORT
		} else if(clazz == short.class || clazz == Short.class) {
			buffer.writeByte(SHORT);
			writeShort((short)value);
		} else if(clazz == short[].class || Short[].class==clazz) {
			byte tag = SHORT|ARRAY;
			buffer.writeByte(tag);
			short[] array = (short[])value;
			writeInt(array.length);
			for(int i = 0; i < array.length; i++) {
				writeShort(array[i]);
			}
		//CHAR
		} else if(clazz == char.class || clazz == Character.class) {
			buffer.writeByte(CHAR);
			writeChar((char)value);
		} else if(clazz == char[].class || Character[].class==clazz) {
			byte tag = CHAR|ARRAY;
			buffer.writeByte(tag);
			char[] array = (char[])value;
			writeInt(array.length);
			for(int i = 0; i < array.length; i++) {
				writeChar(array[i]);
			}
		//INT
		} else if(clazz == int.class || clazz == Integer.class) {
			buffer.writeByte(INT);
			writeInt((int)value);
		} else if(clazz == int[].class) {
			byte tag = INT|ARRAY;
			buffer.writeByte(tag);
			int[] array = (int[])value;
			writeInt(array.length);
			for(int i = 0; i < array.length; i++) {
				writeInt(array[i]);
			}
		//LONG
		} else if(clazz == long.class || clazz == Long.class) {
			buffer.writeByte(LONG);
			writeLong((long)value);
		} else if(clazz == long[].class) {
			byte tag = LONG|ARRAY;
			buffer.writeByte(tag);
			long[] array = (long[])value;
			writeInt(array.length);
			for(int i = 0; i < array.length; i++) {
				writeLong(array[i]);
			}
		//FLOAT
		} else if(clazz == float.class || clazz == Float.class) {
			buffer.writeByte(FLOAT);
			writeFloat((float)value);
		} else if(clazz == float[].class) {
			byte tag = FLOAT|ARRAY;
			buffer.writeByte(tag);
			float[] array = (float[])value;
			writeInt(array.length);
			for(int i = 0; i < array.length; i++) {
				writeFloat(array[i]);
			}
		//DOUBLE
		} else if(clazz == double.class || clazz == Double.class) {
			buffer.writeByte(DOUBLE);
			writeDouble((double)value);
		} else if(clazz == double[].class) {
			byte tag = DOUBLE|ARRAY;
			buffer.writeByte(tag);
			double[] array = (double[])value;
			writeInt(array.length);
			for(int i = 0; i < array.length; i++) {
				writeDouble(array[i]);
			}
		//STRING
		} else if(clazz == String.class) {
			buffer.writeByte(STRING);
			writeString((String)value);
		} else if(clazz == String[].class) {
			byte tag = STRING|ARRAY;
			buffer.writeByte(tag);
			String[] array = (String[])value;
			writeInt(array.length);
			for(int i = 0; i < array.length; i++) {
				writeString(array[i]);
			}
		//ENUM
		} else if(value instanceof Enum) {
			buffer.writeByte(ENUM);
			Enum<?> val = (Enum<?>) value;			
			writeString(val.getClass().getName());
			writeString(val.name());
			
		//COLLECTION LIST SET
		} else if(value instanceof Collection) {
			Collection<?> val = (Collection<?>) value;			
			//判断子类型
			byte tag;
			if(value instanceof ArrayList) tag = LIST;
			else if(value instanceof Set) tag = SET;
			else if(value instanceof LinkedList) tag = LINKLIST;
			else tag = COLLECTION;			
			buffer.writeByte(tag);
			writeInt(val.size());			
			for(Object o : val) {
				this.writeObject(o);
			}		
		//MAP
		} else if(value instanceof LinkedHashMap) {
			buffer.writeByte(LINKHASHMAP);
			Map<?,?> val = (Map<?,?>) value;
			writeInt(val.size());
			for(Entry<?, ?> e : val.entrySet()) {
				Object k = e.getKey();
				Object v = e.getValue();
				this.writeObject(k);
				this.writeObject(v);
			}
		//MAP
		} else if(value instanceof HashMap) {
			buffer.writeByte(HASHMAP);
			Map<?,?> val = (Map<?,?>) value;
			writeInt(val.size());
			for(Entry<?, ?> e : val.entrySet()) {
				Object k = e.getKey();
				Object v = e.getValue();
				this.writeObject(k);
				this.writeObject(v);
			}	
		//MAP
		} else if(value instanceof ConcurrentHashMap) {
			buffer.writeByte(CONHASHMAP);
			Map<?,?> val = (Map<?,?>) value;
			writeInt(val.size());
			for(Entry<?, ?> e : val.entrySet()) {
				Object k = e.getKey();
				Object v = e.getValue();
				this.writeObject(k);
				this.writeObject(v);
			}
		//IDistributedSerilizable接口
		} else if(value instanceof ISerializable) {
			ISerializable seriable = (ISerializable)value;
			buffer.writeByte(DISTRIBUTED);
			writeString(value.getClass().getName());
			seriable.writeTo(this);
		//IDistributedSerilizable[]接口
		} else if(value instanceof ISerializable[]) {
			byte tag = DISTRIBUTED|ARRAY;
			buffer.writeByte(tag);
			writeString(clazz.getComponentType().getName());
			
			ISerializable[] array = (ISerializable[])value;
			writeInt(array.length);
			if (array.length > 0) {
				for (int i=0; i<array.length; ++i) {
					writeObject(array[i]);
				}
			}
		//数组
		} else if (clazz.isArray()) {
			Object[] array = (Object[])value;
			byte tag = OBJECT | ARRAY;
			buffer.writeByte(tag);
			writeInt(array.length);
			writeString(clazz.getComponentType().getName());
			for (int i=0; i<array.length; ++i) {
				write(array[i]);
			}
		
//		} else if (value instanceof PBMessage) {// protobuf消息
//			buffer.writeByte(MSG);
//			PBMessage<?> msg = (PBMessage<?>) value;
//			byte[] bytes = msg.getProtoMsg().toByteArray();
//			writeInt(bytes.length);
//			buffer.writeInt(msg.getId());
//			buffer.writeBytes(bytes);
		//其余一律不支持
		} else {
			throw new DistrSerilizableException("发现无法被Distributed序列化的类型:{}", clazz.getName());
		}
	}

	@Override
	public void writeBytes(byte[] bytes) throws IOException {
		buffer.writeInt(bytes.length);
		buffer.writeBytes(bytes);
	}
	
}
